/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render_obj_widget.hh"
#include "render_params.hh"
#include "render_obj_frame.hh"
#include "render_obj_camera.hh"
#include "event_mng.hh"
#include "ogl.hh"
#include "ogl_font.hh"
#include "time_queue.hh"
#include "key_names.hh"
#include <math.h>

// DEFINES
#define SELECTOR_SOMETHING      IS_SEL_CENTER

#define COLOR_NONSELECT         0.70, 0.30, 0.20, 0.5
#define COLOR_SELECT            1.00, 0.50, 0.40, 0.5
#define COLOR_CSELECT           0.90, 0.40, 0.30, 0.5
#define COLOR_ANIM_USEC         300000

#define WIRE_DISTANCE           40

#define BLEND_FACTOR            1.0
#define BLEND_ATT               0.2
#define BLEND_DIST              5.0
#define BLEND_DIST_FACTOR       (0.5/BLEND_DIST)

#define ROW_POS_VELO            1e-5
#define ROW_POS_VELO_ATT_1      0.999
#define ROW_POS_VELO_ATT_2      0.995

#define INC_PARAMS              4

#define ID                      independent_data


CLASS_INIT(render_obj_widget); // RENDER_OBJ_WIDGET CLASS

render_obj_widget *render_obj_widget::root=NULL;

render_obj_widget::render_obj_widget()
{
    CLASS_CTOR(render_obj_widget);
    ctor();
}

render_obj_widget::~render_obj_widget()
{
    CLASS_DTOR(render_obj_widget);
    dtor();
}

void render_obj_widget::ctor()
{
    dims[IS_X]=5;
    dims[IS_Y]=2;
    dims[IS_Z]=0.4;
    independent=true;
    independent_data=NULL;

    // params
    depth_init=0;
    size[0]=size[1]=0;
    util::ctor_vector(params, n_params, max_params);
}

void render_obj_widget::dtor()
{
    if(independent_data)
    {
	delete independent_data;
	independent_data=NULL;
    }
    util::dtor_vector(params, n_params, max_params);
}

bool render_obj_widget::init(render_mng *ctx)
{
    // actions
    struct
    {
	unsigned act_type, act_seq, act_coord, act_cursor, act_key;
	unsigned key_code, key_status;
	unsigned obj_id, obj_status;
	unsigned selector;
	const char *tip;
    } act[]=
      {
	  {
	      EM_ACTION_TYPE_INT_V0N, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_S, 0,
	      get_type(), IS_STATUS_ANY, IS_SEL_NONE,
	      "Widget: Back"
	  },
	  {
	      EM_ACTION_TYPE_INT_V0P, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_W, 0,
	      get_type(), IS_STATUS_ANY, IS_SEL_NONE,
	      "Widget: Advance"
	  },
	  {
	      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL
	  }
      };

    for(int i=0; act[i].tip; i++)
	ctx->get_event()->action_register(
	    EM_SET_ACTION(act[i].act_type, act[i].act_seq, act[i].act_coord, act[i].act_cursor, act[i].act_key),
	    act[i].key_code, act[i].key_status, act[i].obj_id, act[i].obj_status, act[i].selector, act[i].tip);

    return render_obj::init(ctx);
}

void render_obj_widget::check_independent_data()
{
    if(!independent_data)
    {
	independent_data=new independent_data_str;

	// clear color
	ID->color.set_color(COLOR_NONSELECT);
	render_color_anim::init_color(ID->color_nonselect, COLOR_NONSELECT);
	render_color_anim::init_color(ID->color_select,    COLOR_SELECT);
	render_color_anim::init_color(ID->color_cselect,   COLOR_CSELECT);
	ID->color_anim_usec=COLOR_ANIM_USEC;

	// movement
	ID->depth=0.0;
	ID->velo=ROW_POS_VELO;
	ID->velo_att[0]=ROW_POS_VELO_ATT_1;
	ID->velo_att[1]=ROW_POS_VELO_ATT_2;
    }
}

void render_obj_widget::render_base(render_mng *ctx)
{
    if(independent)
    {
	// this is the root of all children
	root=this;

	// independent data
	check_independent_data();

	// specific cube render
	if(ctx->get_pass()==RENDER_PASS_PRECALC)
	{
	    // get ogl modelview matrix, that will be used multiple times later
	    double mv[IS_MAT4X4];
	    get_modelview_matrix(mv);

	    // precalc pass
	    if(modelview_projection_visibility(0.5*RO_MODULE(dims), mv))
		required_pass=1<<RENDER_PASS_OPAQUE | 1<<RENDER_PASS_SELECT;
	    else
		required_pass=0;

	    // distance to camera
	    ID->distance=sqrtf(get_modelview_distance_nsqr(mv));

	    // 'frontality'
	    ID->infront=-RO_SCA_PROD(&mv[8], &mv[12])/ID->distance;

	    // for content blending
	    ID->front_blend=ID->infront*BLEND_FACTOR-BLEND_ATT-
		(ID->distance>BLEND_DIST?(ID->distance-BLEND_DIST)*BLEND_DIST_FACTOR:0);

	    // pixel to 3D dimensions ratio
	    double separation=4.0;
	    ID->pixel_to_dims_ratio=
		(double)ctx->get_obj_frame()->get_viewport(IS_WH)*
		ctx->get_obj_camera()->get_focal()*0.5/separation;
	    ID->dims_to_pixel_ratio=1.0/ID->pixel_to_dims_ratio;

	    // attraction field
	    if(ID->distance<20)
	    {
		// set action params
		double m[IS_MAT4X4];
		ctx->calculate_hierarchy_modelview_nocam_matrix(m);
		action_attraction_str *attraction=(action_attraction_str *)ctx->get_action_params(sizeof(action_attraction_str));
		attraction->position[IS_X]=m[12]+m[8] *separation;
		attraction->position[IS_Y]=m[13]+m[9] *separation;
		attraction->position[IS_Z]=m[14]+m[10]*separation;
		attraction->lookat[IS_X]=m[8];
		attraction->lookat[IS_Y]=m[9];
		attraction->lookat[IS_Z]=m[10];
		attraction->vertical[IS_X]=m[4];
		attraction->vertical[IS_Y]=m[5];
		attraction->vertical[IS_Z]=m[6];
		attraction->velocity=10/(ID->distance+0.1);

		// call action
		ctx->event_action_from_render_obj(
		    EM_SET_ACTION(EM_ACTION_TYPE_OBJ_CAMERA_ATTRACTOR,
				  EM_ACTION_SEQ_ONCE,
				  EM_ACTION_COORD_NONE,
				  EM_ACTION_CURSOR_NORMAL,
				  EM_ACTION_KEY_NORMAL),
		    IS_TYPE_CAMERA, IS_STATUS_ANY);
	    }
	}
	else if(ctx->get_pass()==RENDER_PASS_OPAQUE)
	{
	    // depth
	    if(status & (IS_STATUS_SELECTED | IS_STATUS_CSELECTED))
	    {
		event_mng *e=ctx->get_event();
		float incs[EM_ACTION_N_VECTOR];
		if(e->action_vector_active())
		{
		    e->action_vector_updatef(incs, ID->velo_att[0]);
		    forced_pass=RENDER_PASS_OPAQUE;
		}
		else
		{
		    e->action_vector_updatef(incs, ID->velo_att[1]);
		    if(e->action_vector_getf(0)!=0.0)
			forced_pass=RENDER_PASS_OPAQUE;
		}
		if((ID->depth+=incs[0]*ID->velo)<0) ID->depth=0;
	    }

	    // color
	    float color_tmp[IS_RGBA];
	    if(ID->color.refresh(color_tmp)) forced_pass=RENDER_PASS_OPAQUE; // require refresh
	    glMaterialfv(GL_FRONT, GL_DIFFUSE, color_tmp);
	    if(ID->infront>0) render_front(ctx);
	    render_back_and_lateral(color_tmp);

	    // 2D params
	    render_set_2d();
	}
	else if(ctx->get_pass()==RENDER_PASS_SELECT)
	{
	    render_cube_select();

	    // 2D params
	    render_set_2d();
	}
    }
}

void render_obj_widget::render_base_end(render_mng *ctx)
{
    if(independent)
    {
	root=NULL;

	if(ctx->get_pass()==RENDER_PASS_OPAQUE || ctx->get_pass()==RENDER_PASS_SELECT)
	{
	    // unset 2D params
	    glPopAttrib();
	}
    }
}

void render_obj_widget::render_set_2d()
{
    // set 2D render params
    glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);
    glDepthMask(GL_FALSE);
    glDisable(GL_POLYGON_OFFSET_FILL);
    glDisable(GL_LIGHTING);
}

void render_obj_widget::render(render_mng *ctx)
{
    render_init(ctx);
    render_base(ctx);
    render_children(ctx);
    render_base_end(ctx);
    render_finish(ctx);
}

bool render_obj_widget::event(render_mng *ctx)
{
    render_obj::event(ctx);

    if(independent)
    {
	if(ctx->get_event_last())
	{
	    check_independent_data();

	    // status event: IS_STATUS_*
	    if(status & IS_STATUS_SELECTED)
		ID->color.set_color_dest(ID->color_select, ID->color_anim_usec);
	    else if(status & IS_STATUS_CSELECTED)
		ID->color.set_color_dest(ID->color_cselect, ID->color_anim_usec);
	    else
		ID->color.set_color_dest(ID->color_nonselect, ID->color_anim_usec);

	    ctx->set_forced_pass(RENDER_PASS_OPAQUE, this);
	}
	else
	{
	    event_mng *e=ctx->get_event();
	    unsigned action;
	    if((action=e->get_action_last()))
	    {
		// action dispatcher
		if(EM_GET_ACTION_EVENT(action)==EM_ACTION_EVENT_VECTOR)
		{
		    // force precalc pass
		    ctx->set_forced_pass(RENDER_PASS_OPAQUE, this);
		    return true;
		}
	    }
	}
    }
    return false; // not managed
}

unsigned render_obj_widget::get_type()
{
    return IS_TYPE_WIDGET;
}

void render_obj_widget::render_back_and_lateral(float color[IS_RGBA])
{
    double d[IS_2D]={dims[IS_X]*0.5, dims[IS_Y]*0.5};

    // draw lateral
    render_lateral(d);

    // draw back
    glBegin(GL_QUADS);
    glNormal3d(0, 0, -1);
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glVertex3d( d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glEnd();

    // wire frame
    if(ID->distance<WIRE_DISTANCE)
    {
	float color_tmp[IS_RGBA];
	for(int i=0; i<IS_3D; i++) color_tmp[i]=color[i]*0.3;
	glPushAttrib(GL_POLYGON_BIT);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, color_tmp);
	glPolygonMode(GL_FRONT, GL_LINE);
	render_lateral(d);
	glPopAttrib();
    }
}

void render_obj_widget::render_lateral(double d[IS_2D])
{
    glBegin(GL_QUAD_STRIP);
    glNormal3d(1, 1, 0);
    glVertex3d( d[IS_X],  d[IS_Y],  0);
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glNormal3d(-1, 1, 0);
    glVertex3d(-d[IS_X],  d[IS_Y],  0);
    glVertex3d(-d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glNormal3d(-1, -1, 0);
    glVertex3d(-d[IS_X], -d[IS_Y],  0);
    glVertex3d(-d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glNormal3d(1, -1, 0);
    glVertex3d( d[IS_X], -d[IS_Y],  0);
    glVertex3d( d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glNormal3d(1, 1, 0);
    glVertex3d( d[IS_X],  d[IS_Y],  0);
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glEnd();
}

void render_obj_widget::render_front(render_mng *ctx)
{
    double d[IS_2D]={dims[IS_X]*0.5, dims[IS_Y]*0.5};

    // draw front
    glBegin(GL_QUADS);
    glNormal3d(0, 0, 1);
    glVertex2d( d[IS_X],  d[IS_Y]);
    glVertex2d(-d[IS_X],  d[IS_Y]);
    glVertex2d(-d[IS_X], -d[IS_Y]);
    glVertex2d( d[IS_X], -d[IS_Y]);
    glEnd();
}

void render_obj_widget::render_cube_select()
{
    double d[IS_2D]={dims[IS_X]*0.5, dims[IS_Y]*0.5};

    // lateral
    glBegin(GL_QUAD_STRIP);
    glVertex3d( d[IS_X],  d[IS_Y],  0);
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X],  d[IS_Y],  0);
    glVertex3d(-d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X], -d[IS_Y],  0);
    glVertex3d(-d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d( d[IS_X], -d[IS_Y],  0);
    glVertex3d( d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d( d[IS_X],  d[IS_Y],  0);
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glEnd();

    glBegin(GL_QUADS);

    // back
    glVertex3d( d[IS_X],  d[IS_Y], -dims[IS_Z]);
    glVertex3d( d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X], -d[IS_Y], -dims[IS_Z]);
    glVertex3d(-d[IS_X],  d[IS_Y], -dims[IS_Z]);

    // front
    glVertex2d( d[IS_X],  d[IS_Y]);
    glVertex2d(-d[IS_X],  d[IS_Y]);
    glVertex2d(-d[IS_X], -d[IS_Y]);
    glVertex2d( d[IS_X], -d[IS_Y]);

    glEnd();
}

void render_obj_widget::add_param(widget_params *param)
{
    // dicotomic search
    int ini=0, end=n_params;
    while(end>ini)
    {
	int mid=(ini+end)>>1;
	if(params[mid].depth>=param->depth) end=mid; else ini=mid+1;
    }
    if(ini>=n_params || params[ini].depth!=param->depth)
    {
	// new param at diferent depth
	util::inc_vector(params, n_params, max_params, INC_PARAMS);

	// make space
	if(n_params-ini-1) memmove(&params[ini+1], &params[ini], (n_params-ini-1)*sizeof(widget_params));
    }

    // copy new param
    memcpy(&params[ini], param, sizeof(widget_params));
}

void render_obj_widget::dump_params()
{
    for(int i=0; i<n_params; i++)
	params[i].dump();
}

void render_obj_widget::widget_params::dump()
{
    const char *align_x[]={"left", "center", "right", NULL};
    const char *align_y[]={"up", "center", "down", NULL};
    printf("%4d %-5s %-5s %4.2f %4.2f %4.2f %4.2f %6.3f\n",
	   depth, align_x[align[IS_X]], align_y[align[IS_Y]],
	   text_color[IS_RED],
	   text_color[IS_GREEN],
	   text_color[IS_BLUE],
	   text_color[IS_ALPHA],
	   text_size);
}
